moment = require 'moment'
co = require 'co'
_ = require 'lodash'

mongo = require '../storage/Mongo'

log = require '../log'
util = require '../util'

pageViewCache = []
pageViewCacheCount = 0

shareCache = []
shareCacheCount = 0

nocookieView = []
nocookieViewCount = 0

exports.gPersist = ->
    log.system.info 'persist promotion record'
    yield from gPersistPageView()
    yield from gPersistShare()
    yield from gPersistNoCookieView()

gPersistPageView = ->
    return unless pageViewCache.length
    pv = pageViewCache
    pageViewCache = []
    pageViewCacheCount = 0

    try
        db = yield mongo.pDatabase()
        c = db.collection 'F_PromotionPageView'
        yield c.insertMany(pv)
    catch e
        log.system.error e, 'gPersistPageView'

gPersistShare = ->
    return unless shareCache.length
    sh = shareCache
    shareCache = []
    shareCacheCount = 0

    try
        db = yield mongo.pDatabase()
        c = db.collection 'F_PromotionShareTrack'
        yield c.insertMany(sh)
    catch e
        log.system.error e, 'gPersistShare'

gPersistNoCookieView = ->
    return unless nocookieView.length
    view = nocookieView
    nocookieView = []
    nocookieViewCount = 0

    try
        db = yield mongo.pDatabase()
        c = db.collection 'F_PromotionNocookieView'
        yield c.insertMany(view)
    catch e
        log.system.error e, 'gPersistNoCookieView'

exports.record = (promotionName, channel, uid, userIp, userAgent, now)->
    channel = '_unknown' unless channel

    if channel == uid
        pageViewCache.push {promotion: promotionName, uid, userIp, userAgent, occurOn: now, _createdOn: now}
        pageViewCacheCount++
    else
        shareCache.push {promotion: promotionName, channel, uid, userIp, userAgent, occurOn: now, _createdOn: now}
        shareCacheCount++

    co(exports.gPersist) if shareCacheCount > 10 || pageViewCacheCount > 10

exports.recordNoCookieView = (promotionName, channel, userIp, userAgent, now)->
    channel = '_unknown' unless channel

    nocookieView.push {promotion: promotionName, channel, userIp, userAgent, occurOn: now, _createdOn: now}
    nocookieViewCount++
    co(exports.gPersist) if nocookieViewCount > 8

exports.gGetReport = (promotions, durationType)->
    criteria = {promotions: promotions}
    criteria.durationType = durationType if durationType

    db = yield mongo.pDatabase()
    c = db.collection 'F_PromotionPVReport'
    cursor = c.find(criteria)
    yield cursor.toArray()

exports.gBuildReport = (promotionName)->
    log.system.info 'build report for promotion ' + promotionName

    # 刷出缓存
    yield from gPersistPageView()
    yield from gPersistShare()

    db = yield mongo.pDatabase()

    {shareTree, toMyParent, nodes} = yield from gBuildShareTree promotionName, db

    viewCounts = yield from gCountView(promotionName, db, toMyParent)

    {shareTree, viewCounts, nodes}

gBuildShareTree = (promotionName, db)->
    toMyParent = {}

    c = db.collection 'F_PromotionShareTrack'
    cursor = c.find({promotion: promotionName}).sort({occurOn: 1})
    shareRecordCount = 0
    while yield cursor.hasNext()
        shareRecordCount++
        share = yield cursor.next()
        parent = toMyParent[share.uid]
        toMyParent[share.uid] = share.channel unless parent # 按时间升序获取分享记录后，将第一个分享记录看做来源，忽略后续

    log.system.info "share records count: " + shareRecordCount

    # 对于根 channel 在 toMyParent 中无记录
    # toMyParent 的键集和值集的大小不同。键集比值集大；
    # 如极端情况，一个链接被4个人访问。toMyParent 有4项，只有1个键

    nodes = {}
    rootNames = new Set() # 根节点列表，可能有多个根
    for child, parent of toMyParent
        childNode = util.setIfNone(nodes, child, {name: child, children: []})
        parentNode = util.setIfNone(nodes, parent, {name: parent, children: []})

        parentNode.children.push childNode

        rootNames.add parent unless toMyParent[parent] # 若没有父母

    roots = (nodes[name] for name in Array.from(rootNames)) # 根节点可能有多个
    nodes._root = {name: '_root', children: roots}

    {shareTree: nodes._root, toMyParent, nodes}

gCountView = (promotionName, db, toMyParent)->
    views = {}

    c = db.collection 'F_PromotionPageView'
    cursor = c.find({promotion: promotionName})
    pvRecordCount = 0
    while yield cursor.hasNext()
        pvRecordCount++
        pv = yield cursor.next()

        occurOnM = moment pv.occurOn
        dayM = occurOnM.startOf('day')
        dayYMD = dayM.year() * 10000 + dayM.month() * 100 + dayM.date()

        for durationType in ["_all", dayYMD]
            viewsByTime = util.setIfNone views, durationType, {}

            channel = pv.uid
            while channel != '__stop__'
                viewsByTimeByChannel = util.setIfNone viewsByTime, channel, {}
                unless viewsByTimeByChannel.pv?
                    viewsByTimeByChannel.pv = 0
                    viewsByTimeByChannel.uidSet = new Set()
                    viewsByTimeByChannel.ipSet = new Set()

                viewsByTimeByChannel.pv++
                viewsByTimeByChannel.uidSet.add pv.uid
                viewsByTimeByChannel.ipSet.add pv.userIp

                channel = if channel == '_root' then '__stop__' else (toMyParent[channel] || '_root')

    log.system.info "pv records count: " + pvRecordCount

    timePoints = _.keys(views).sort()
    for timePoint in timePoints
        viewsByTime = views[timePoint]
        for channel, viewsByTimeByChannel of viewsByTime
            viewsByTimeByChannel.viewCount = viewsByTimeByChannel.pv
            viewsByTimeByChannel.userCount = viewsByTimeByChannel.uidSet.size
            viewsByTimeByChannel.ipCount = viewsByTimeByChannel.ipSet.size
            delete viewsByTimeByChannel.ipSet
            delete viewsByTimeByChannel.uidSet
            false

    views

exports.gGetPromotionChannels = (promotionName)->
    db = yield mongo.pDatabase()
    c = db.collection 'F_PromotionChannels'
    pcs = yield c.findOne({_id: promotionName})

    if pcs?.channels?.length
        channels = pcs.channels

        report = yield from exports.gBuildReport(promotionName)
        reportOfAllDays = report.viewCounts._all
        for c in channels
            reportOfChannel = reportOfAllDays[c.c]
            if reportOfChannel
                c.pv = reportOfChannel.viewCount
                c.uv = reportOfChannel.userCount
                c.ip = reportOfChannel.ipCount

    pcs

exports.gPutPromotionChannels = (promotionName, pcs)->
    pcs._modifiedOn = new Date()
    pcs._id = promotionName

    db = yield mongo.pDatabase()
    c = db.collection 'F_PromotionChannels'
    yield c.updateOne({_id: promotionName}, {$set: pcs}, {upsert: true})




